#ifndef TPL_AGENT_GRAPH_H
#define TPL_AGENT_GRAPH_H

#include <iostream>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>
#include <dlink.H>
#include <tpl_concurrent_graph.H>
#include <tpl_dynArray.H>
#include <tpl_arrayQueue.H>

using namespace Aleph;
using namespace std;

namespace Aleph {

#define STOPPED 0
#define RUNNING 1
#define SUSPENDED 2
#define MAX_THREADS 8

  template <typename Agent_Info> class Agent;
  
  template <typename Node_Info> class Agent_Node;  
  
  template <typename Arc_Info> class Agent_Arc;
  
  template <typename Arc_Agents, typename Node_Agents, typename Agent_Type> class Agent_Graph;
  
  template <typename Agent_Info> struct Agent   {
    Agent_Info info;
    int status;
    Dlink agent_link;
    Dlink schedule_link;
    bool in_node;
    void * location;
    typedef void (*Transit)(void *, void *);
    Transit transit; 
  public:
    Agent(Agent_Info agent_info, Transit transit) : info (agent_info), transit(transit) { }
    ~Agent(){ }
    void execute(void * graph)
    { 
      transit(graph, this); 
    }
 
    LINKNAME_TO_TPL (Agent, agent_link, A_Info);  
    LINKNAME_TO_TPL (Agent, schedule_link, A_Info);

    Agent_Info & get_info() 
    {
      return info;
    }
  };  
  
  template <typename Agents_Node_Info> 
  struct Agent_Node :  public Concurrent_Node <Agents_Node_Info>
  {
    typedef Concurrent_Node<Agents_Node_Info> Parent_Node;
    typedef Agents_Node_Info Node_Type;
    Agent_Node() : Parent_Node() { }
    Agent_Node(const Node_Type & info) : Parent_Node(info) {}
    ~Agent_Node(){ }
  }; 
  
  
  template <typename Agents_Arc_Info> 
  struct Agent_Arc : public Concurrent_Arc<Agents_Arc_Info>    {
    typedef Concurrent_Arc<Agents_Arc_Info> Parent_Arc;
    typedef Agents_Arc_Info Arc_Type;
    Agent_Arc() : Parent_Arc() { } 
    virtual ~Agent_Arc(){ } 
  };
  

  
  template <typename Node,typename Arc, typename Agent_Type > 
  struct Agent_Graph : public Concurrent_Graph<Node, Arc>
  {
  private:
    int thread_count;
    //DynArray<pthread_t*> threads;
    pthread_t tabla_thr[MAX_THREADS];//tabla de numero de threads
    int status ;
    pthread_mutex_t sch_mutex;
    sem_t sch_semaphore;
  public:
    typedef Concurrent_Graph< Node, Arc > Parent_Graph;
    typedef typename Node::Node_Type Node_Type; 
    typedef typename Arc::Arc_Type Arc_Type;
    typedef Agent<Agent_Type> AgentT;
    
    /** Dlinks a las listas de nodos arcos y agentes **/
    
    Dlink agent_list; // lista de agentes
    long num_agents;
    
    Dlink ready_queue; // cola de agentes
    long num_agents_ready; 
    
    Dlink suspended_queue; // cola de agentes
    long num_agents_suspended;  
    
    /** Metodos para los nodos **/ 
   
  public:
   /**Funciones de los agentes **/
    const long get_num_agents() const {return num_agents;}
   
    Node * get_agent_node_location (AgentT * agent) 
    {
      if (agent->in_node) 
      {
        return static_cast<Node*>(agent->location);
      }
      return NULL;
    }

    void set_agent_node_location (AgentT * agent, Node * node)
    {
      agent->in_node = true;
      agent->location = node;
    }

    Arc * get_agent_arc_location (AgentT * agent)
    {
      if (!agent->in_node) 
      {
        return static_cast<Arc*>(agent->location);
      }
      return NULL;
    }

    void set_agent_arc_location(AgentT * agent, Arc * arc)
    {
      agent->in_node = false;
      agent->location = arc;
    }

    bool is_agent_in_node (AgentT * agent)
    {
      return agent->in_node;
    }
 

    AgentT * get_first_agent ()
    {
      if(get_num_agents() == 0)
	      throw std::range_error("Graph has not agents ");
      return Agent<Agent_Type>::template agent_link_to_tpl <Agent, Agent_Type> (&*agent_list.get_next());
    }
    
    void insert_agent(AgentT * agent)
    {
      // if(agent == NULL){
      //   if(not node_in_graph(node));
      //	throw std::range_error("Graph has not agents");}
      ++num_agents;
      Dlink l;
      l=agent->agent_link;
      Dlink * lptr;
      lptr = & l;
      //  agent->execute();
      // cout<<"Agentes : "<< num_agents<<endl;
      agent_list.append(lptr);
      // Agent<Agent_Type>::template dlink_to_tpl <Agent, Agent_Type> (&*agent_list.append(agent));
    }
    
    void remove_agent (AgentT * agent)
    { 
      if(!agent->status){
	      agent->agentlink.del();
	      delete agent;
      }
    }
    
    /**Funciones para el Grafo **/

    const long get_num_agents_ready(){ return num_agents_ready;}
    const long get_num_agents_suspended(){ return num_agents_suspended;}
    
    Agent_Graph() 
    {

      init_mutex(sch_mutex);
      sem_init(&sch_semaphore, 0, 0);
      status =RUNNING; 
    }
    // Agent_Graph(){}
    ~Agent_Graph(){ } 
    
    AgentT * create_agent_in_node(Agent_Type agent_type, void (*Fnct)(void *, void *), Node * node, bool suspended = false  )
    {
      Agent <Agent_Type> * agent = new Agent<Agent_Type> ( agent_type ,  Fnct );
      agent->status=(int)(suspended);
      
      Dlink * lptr;
      //      lptr= &  agent->agent_link;
      lptr = & agent->schedule_link;
      agent->in_node = true;
      agent->location= node;
      if(!suspended)
	    { 
        ++num_agents_ready; 
        sem_post(&sch_semaphore);
	      ready_queue.append(lptr);
	    }
      else
	    {
	      suspended_queue.append(lptr);
	      ++num_agents_suspended; 
	    }
      
      //1 Crear el objeto Agent(Asignar los valores )
      //2 Colocarlo en la cola correspondiente (meter en la cola del nodo)
      //meter en la cola de ejecucion
      //si hay hilos diponibles despertarlo.
      return agent;
    }
    
    AgentT * create_agent_in_arc(Agent_Type * agent_type, void (*Fnct)(void *, void *), Arc * arc, bool suspended = false  )
    {
      // cout<<"Creando agentes en arco"<<endl; 
      Agent <Agent_Type> agent ( agent_type ,  Fnct );
      agent.status=(int)(suspended);
      agent.in_node = false;
      agent.location= arc;
      Dlink * lptr;
      lptr= &  agent.agent_link;
      if(!suspended)
	    {
        sem_post(&sch_semaphore);
	      ready_queue.append(lptr);
	      ++num_agents_ready; 
    	}
      else
	    {
	      suspended_queue.append(lptr);
	      ++num_agents_suspended;  
	    }
      //1 Crear el objeto Agent(Asignar los valores )
      //2 Colocarlo en la cola correspondiente (meter en la cola del nodo)
      //meter en la cola de ejecucion
      //si hay hilos diponibles despertarlo.
    }
    
    int suspend_agent(AgentT * agent){
      //cambio de status y enviar a la cola de suspendidos
      //retorna un valor si lo suspendió
      return 0;
    }

    template <class Equal>
    AgentT * search_agent(const Agent_Type & agent)
    {
      AgentT * result = NULL;
      for (Dlink::Iterator it(agent_list); it.has_current(); it.next()) 
      {
        result = AgentT::template agent_link_to_tpl <Agent, Agent_Type>(it.get_current());
        if (Equal() (result->get_info(), agent)) 
        {
          return result;
        }
        
      }
      return NULL;
    }
   
    AgentT * search_agent(const Agent_Type & agent)
    {
       return search_agent<Aleph::equal_to<Agent_Type> >(agent);
    }

    void start_graph() 
    {
     
      // cout<<"start_graph()"<<endl;
      for (int i = 0; i < MAX_THREADS; i++) 
	    {
	      pthread_t * t;
	      int result = pthread_create(&tabla_thr[i], NULL, Agent_Graph::run, this);
	          if (result == 0) 
		 {
		    cout<<" Thread "<<i<<endl; 
		   //threads[i] = t;
		 }
	    }
    }

    void stop_graph()
    {
      set_status(STOPPED);
    }
    
    void set_thread_count(int thread_count)
    {
      this.thread_count = thread_count;
    }
    
    void get_thread_count()
    {
      return this.thread_count;
    }
    
    int get_status()
    {
      CRITICAL_SECTION(sch_mutex);
      return status;
    }

    void set_status(int value)
    {
      CRITICAL_SECTION(sch_mutex);
      status = value;
    }

  void (*ptrvo);

    AgentT * remove_next_ready_agent() 
    {
      // CRITICAL_SECTION(sch_mutex);
 
      if (not ready_queue.is_empty())
	{
	 
	  AgentT * result =  AgentT::template schedule_link_to_tpl <Agent, Agent_Type>(ready_queue.get_next());
	  ready_queue.remove_next();
	  num_agents_ready--;
	  //Para probar el LINK_TO_TPL las siguientes 3 lineas 
	   cout<<endl<<"Sacando agentes"<<endl;//<<result;
	  cout << result->get_info().descripcion << endl;
	  // cout<<result->in_node;
	  return result;
	}
      return NULL;
    }
    

    void insert_ready_agent(AgentT* agent)
    {
      //  CRITICAL_SECTION(sch_mutex);
      ready_queue.append(&agent->schedule_link);
      num_agents_ready++;
    }
    
    void suspend_graph()
    {
      set_status(SUSPENDED);
    }
    void resume_graph()
    {
      set_status(RUNNING);
    }
    
    static void * run(void * cookie) 
    {
      
      // Lógica de ejecución de agentes
      
      Agent_Graph * graph = static_cast<Agent_Graph*>(cookie);
      
       
	  AgentT * next_agent = graph->remove_next_ready_agent();
	  if (next_agent != NULL)
	    {
	      next_agent->execute(graph);
	      graph->insert_ready_agent(next_agent);
	    }
	      

      return NULL;
    }

    void run_single() 
    {
       // Lógica de ejecución de agentes
      
 //     Agent_Graph * graph = static_cast<Agent_Graph*>(cookie);
      
       for (int i = 0; i < 30; i++)
       {
	      AgentT * next_agent = remove_next_ready_agent();
	      if (next_agent != NULL)
	      {
	        next_agent->execute(this);
	        insert_ready_agent(next_agent);
	      }
       }
    }

  };
}

#endif
